package cn.yajienet.data.elasticsearch.init;

import cn.yajienet.data.elasticsearch.ElasticSearchConstant;
import cn.yajienet.data.elasticsearch.annotation.Document;
import cn.yajienet.data.elasticsearch.annotation.LoadOrder;
import cn.yajienet.data.elasticsearch.context.ElasticSearchContext;
import cn.yajienet.data.elasticsearch.data.LoadData;
import cn.yajienet.data.elasticsearch.document.InitializeDocument;
import cn.yajienet.data.elasticsearch.dto.DocumentEntity;
import cn.yajienet.data.elasticsearch.listener.ElasticSearchListener;
import cn.yajienet.data.elasticsearch.mapping.Mapping;
import cn.yajienet.data.elasticsearch.properties.ElasticSearchProperties;
import cn.yajienet.data.elasticsearch.setting.Setting;
import cn.yajienet.data.elasticsearch.task.TaskManagementCenter;
import cn.yajienet.data.elasticsearch.utils.ClassScan;
import cn.yajienet.data.elasticsearch.utils.GeneralTools;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.index.reindex.ReindexRequest;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.ApplicationContext;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static cn.yajienet.data.elasticsearch.ElasticSearchConstant.DEFAULT_LOAD_DATA_ORDER;

/**
 * @Author Wang Chenguang
 * @Email wcg.chen@foxmail.com
 * @Date on 2021/12/19
 * @Version 1.0.0
 * @Description 抽象初始化方法
 */
@Slf4j
public abstract class AbstractRunInitialization {

    /**
     * 所有文档对应的数据加载对象
     */
    protected final Map<Class<?>, LoadData> loadDataBeans = new HashMap<>();

    /**
     * 初始化错误的文档
     */
    protected final List<Class<?>> errorDocument = new ArrayList<>();

    @Autowired
    protected RestHighLevelClient restHighLevelClient;
    @Autowired
    protected ElasticSearchProperties elasticSearchProperties;
    @Autowired
    protected ApplicationContext applicationContext;
    @Autowired
    protected RestTemplate restTemplate;

    /**
     * 运行入口
     *
     * @param args {@link ApplicationArguments}
     * @author Wang Chenguang
     * @date 2021/12/19
     */
    public abstract void init(ApplicationArguments args);

    /**
     * 扫面配置的路径，并打印已经扫描到的文档
     *
     * @author Wang Chenguang
     * @date 2021/12/19
     */
    protected void scanDocumentScanPackages() {
        List<String> documentScans = elasticSearchProperties.getDocumentScanPackages();
        if (!CollectionUtils.isEmpty( documentScans )) {
            List<String> basePackages = documentScans.stream().filter( StringUtils::hasLength ).collect( Collectors.toList() );
            if (!basePackages.isEmpty()) {
                String[] propertiesBasePackages = new String[basePackages.size()];
                basePackages.toArray( propertiesBasePackages );
                Set<Class<?>> documentScanClass = ClassScan.scan( propertiesBasePackages, Document.class );
                if (!CollectionUtils.isEmpty( documentScanClass )) {
                    ElasticSearchContext.addDocumentsClassAll( documentScanClass );
                }
            }
        }
        Set<Class<?>> classSet = ElasticSearchContext.getDocumentsClass();
        if (CollectionUtils.isEmpty( classSet )) {
            log.warn( "EnableElasticSearchServices annotation not scan." );
        } else {
            classSet.forEach( aClass -> log.info( "EnableElasticSearchServices annotation scans documents: {}", aClass.getName() ) );
        }
    }

    /**
     * 初始化数据
     *
     * @author Wang Chenguang
     * @date 2021/10/17
     */
    protected void initializeData(boolean cache) {
        for (Map.Entry<Class<?>, LoadData> entry : loadDataBeans.entrySet()) {
            Class<?> documentClass = entry.getKey();
            LoadData loadData = entry.getValue();
            if (errorDocument.contains( documentClass )) {
                log.error( "没有初始化文档[{}]，跳过加载数据！", documentClass.getName() );
            } else {
                loadData.setClient( restHighLevelClient );
                loadData.setRestTemplate( restTemplate );
                Document  document = documentClass.getAnnotation( Document.class );
                DocumentEntity documentEntity = GeneralTools.toDocumentEntity( document, documentClass.getSimpleName(), elasticSearchProperties, cache );
                if (Objects.nonNull( documentEntity )){
                    loadData.setDocumentEntity( documentEntity );
                    LoadOrder loadOrder = loadData.getClass().getAnnotation( LoadOrder.class );
                    if (Objects.isNull( loadOrder )) {
                        ElasticSearchContext.addTasK( DEFAULT_LOAD_DATA_ORDER, loadData );
                    } else {
                        ElasticSearchContext.addTasK( loadOrder.value(), loadData );
                    }
                    log.info( "文档[{}]已经成功加载到任务列......", documentClass.getName() );
                }else {
                    log.info( "文档[{}]没有加载到任务列......", documentClass.getName() );
                }
            }
        }
    }

    /**
     * 查找文档对应的初始化数据组件
     *
     * @author Wang Chenguang
     * @date 2021/10/17
     */
    protected void scanLoadData() {
        List<Class<?>> classList = new ArrayList<>( ElasticSearchContext.getDocumentsClass() );
        ObjectProvider<LoadData> loadDataObjectProvider = applicationContext.getBeanProvider( LoadData.class );
        for (LoadData loadData : loadDataObjectProvider) {
            Class<?> aClass = loadData.getDocumentClass();
            if (loadDataBeans.containsKey( aClass )) {
                log.error( "文档[{}]已经加载到对应初始化数据对象，跳过当前[{}]load data.....", aClass.getSimpleName(), loadData.getClass().getSimpleName() );
                continue;
            }
            if (classList.contains( aClass )) {
                loadDataBeans.put( aClass, loadData );
                classList.remove( aClass );
            } else {
                log.warn( "初始化数据[{}]定义文档[{}]未扫描到，文档可能配置有误！", loadData.getClass().getName(), aClass.getName() );
            }
        }
        for (Class<?> aClass : classList) {
            log.warn( "文档[{}]未定义初始化数据功能，若需要请继承AbstractLoadDataAdapter类！", aClass.getName() );
        }
    }

    protected void startLoadData() {
        // 文档初始化完成，启动任务管理中心
        TaskManagementCenter taskManagementCenter = new TaskManagementCenter( restTemplate );
        taskManagementCenter.start();
        try {
            taskManagementCenter.join();
        } catch (InterruptedException e) {
            log.error( e.getMessage(), e );
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 配置基本InitializeDocument属性
     *
     * @param document {@link Document}
     * @param aClass {@link Class}
     * @return cn.yajienet.data.elasticsearch.document.InitializeDocument
     * @author Wang Chenguang
     * @date 2021/12/20
     */
    protected InitializeDocument setInitializeDocument(Document document, Class<?> aClass) throws InstantiationException, IllegalAccessException {
        InitializeDocument initializeDocument = document.initializeDocument().newInstance();
        Setting defaultSetting = document.setting().newInstance();
        defaultSetting.setDocumentClass( aClass );
        Mapping autoMapping = document.mapping().newInstance();
        autoMapping.setDocumentClass( aClass );
        initializeDocument.setClient( restHighLevelClient );
        initializeDocument.setDocumentClass( aClass );
        initializeDocument.setSetting( defaultSetting );
        initializeDocument.setMapping( autoMapping );
        return initializeDocument;
    }

    /**
     * 判断索引是否存在
     *
     * @param index 索引名字
     * @return boolean
     * @author Wang Chenguang
     * @date 2021/12/20
     */
    protected boolean existIndex(String index) throws IOException {
        GetIndexRequest indexRequest = new GetIndexRequest( index );
        return restHighLevelClient.indices().exists( indexRequest, RequestOptions.DEFAULT );
    }

    /**
     * 删除索引
     *
     * @param index 索引名字
     * @return boolean
     * @author Wang Chenguang
     * @date 2021/12/20
     */
    protected boolean deleteIndex(String index) throws IOException {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest( index );
        return restHighLevelClient.indices().delete( deleteIndexRequest, RequestOptions.DEFAULT ).isAcknowledged();
    }

    /**
     * 索引实体
     *
     * @param documentEntity {@link DocumentEntity}
     * @author Wang Chenguang
     * @date 2021/12/20
     */
    protected void updateCacheIndexName(DocumentEntity documentEntity) throws IOException {
        ReindexRequest reindexRequest = new ReindexRequest();
        reindexRequest.setSourceIndices( documentEntity.getIndex() + ElasticSearchConstant.CACHE_INDEX_SUFFIX );
        reindexRequest.setSourceBatchSize( 5000 );
        reindexRequest.setDestIndex( documentEntity.getIndex() );
        reindexRequest.setDestOpType( "index" );
        reindexRequest.setConflicts( "proceed" );
        restHighLevelClient.reindex( reindexRequest, RequestOptions.DEFAULT );
    }


    public void listener(ApplicationArguments args){
        ObjectProvider<ElasticSearchListener> listeners = applicationContext.getBeanProvider( ElasticSearchListener.class );
        for (ElasticSearchListener listener : listeners) {
            listener.run( args );
        }
    }

}
